package com.alkaid.user.config;

import com.alkaid.commons.core.CacheKeyDefinition;
import com.alkaid.commons.msg.MsgDefinition;
import com.alkaid.commons.entity.Authority;
import com.alkaid.commons.utils.JsonResult;
import com.alkaid.commons.utils.JwtUtils;
import com.alkaid.user.filter.JwtAuthorizationFilter;
import com.alkaid.user.filter.LoginFilter;
import com.alkaid.user.mapper.AuthorityMapper;
import com.alkaid.user.mapper.UserMapper;
import com.alkaid.user.service.impl.UserDetailsImpl;
import com.alkaid.user.utils.RedisUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import java.util.List;

/**
 * @author Kylin
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private UserMapper userMapper;

    private AuthorityMapper authorityMapper;

    private RedisUtils redisUtils;
    @Autowired
    public void setUserMapper(UserMapper userMapper) {
        this.userMapper = userMapper;
    }
    @Autowired
    public void setAuthorityMapper(AuthorityMapper authorityMapper) {
        this.authorityMapper = authorityMapper;
    }
    @Autowired
    public void setRedisUtils(RedisUtils redisUtils) {
        this.redisUtils = redisUtils;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable();
        // 添加自定义拦截器
        http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class)
                .addFilter(new JwtAuthorizationFilter(authenticationManager()));
        http.authorizeRequests()
                .antMatchers("/api/user/reg").permitAll()
                .antMatchers("/api/user/sms").permitAll()
                .antMatchers("/api/user/check_nickname").permitAll()
                .anyRequest().authenticated()
                .and().formLogin();
        http.logout().logoutUrl("/api/user/logout").logoutSuccessHandler((req, resp, e) -> {
            // 退出登录
            JwtUtils.responseStatus(resp, new JsonResult<>(200, null, MsgDefinition.LOGOUT_SUCCESS));
        });
        http.exceptionHandling()
                .authenticationEntryPoint((req, resp, e) -> {
                    // 请先登录
                    JwtUtils.responseStatus(resp, new JsonResult<>(201, null, MsgDefinition.LOGIN_PLEASE));
                })
                .accessDeniedHandler((req, resp, e) -> {
                    // 权限不足
                    JwtUtils.responseStatus(resp, new JsonResult<>(403, null, MsgDefinition.AUTHORITY_LOSS));
                });
    }

    /**
     * 自定义 UsernamePasswordAuthenticationFilter 过滤器
     */
    @Bean
    LoginFilter loginFilter() throws Exception {
        LoginFilter loginFilter = new LoginFilter();
        // 前端的登录请求地址
        loginFilter.setFilterProcessesUrl("/api/user/login");
        // 登录成功后返回给前端的json数据
        loginFilter.setAuthenticationSuccessHandler((req, resp, authentication) -> {
            boolean isRememberMe = "true".equals(req.getHeader("rememberMe"));
            User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
            com.alkaid.commons.entity.User userDO = userMapper.findUserByUsername(user.getUsername());
            List<Authority> authorities = authorityMapper.findAuthorityByUserId(userDO.getId());
            userDO.setAuthorities(authorities);
            String jwt = new JwtUtils().createJwt(userDO, isRememberMe);
            // 登陆成功
            JwtUtils.responseStatus(resp, new JsonResult<>(200, jwt, MsgDefinition.LOGIN_SUCCESS));
        });
        // 登录失败后返回给前端的json数据
        loginFilter.setAuthenticationFailureHandler((req, resp, e) -> {
            boolean isUsername = "username".equals(req.getHeader("grant_type"));
            JsonResult<Void> result = UserDetailsImpl.getResult();
            if (result == null) {
                String[] str = req.getParameter("username").split("-");
                String username = str[1];
                String password = getPassword().encode(req.getParameter("password"));
                if (isUsername) {
                    com.alkaid.commons.entity.User userDO = userMapper.findUserByUsername(username);
                    System.out.println(getPassword().matches(password, userDO.getPassword()));
                    result = getPassword().matches(password, userDO.getPassword()) ?
                            // 登陆失败
                            new JsonResult<>(202, null, MsgDefinition.LOGIN_FAILED) :
                            // 验证码错误
                            new JsonResult<>(201, null, MsgDefinition.USERNAME_PASSWORD_ERROR);
                } else {
                    password = req.getParameter("password");
                    String pwd = redisUtils.getFromRedis(CacheKeyDefinition.SMS_KEY_MAP.get("login") + username).toString();
                    result = password.equals(pwd) ?
                            // 登陆失败
                            new JsonResult<>(202, null, MsgDefinition.LOGIN_FAILED) :
                            // 验证码错误
                            new JsonResult<>(201, null, MsgDefinition.SMS_ERROR);
                }
            }
            JwtUtils.responseStatus(resp, result);
        });
        loginFilter.setAuthenticationManager(authenticationManagerBean());
        return loginFilter;
    }

    @Bean
    public PasswordEncoder getPassword() {
        return new BCryptPasswordEncoder();
    }
}
